/******************************************************************/
/*                                                                */
/*  Winpooch : Windows Watchdog                                   */
/*  Copyright (C) 2004-2005  Benoit Blanchon                      */
/*                                                                */
/*  This program is free software; you can redistribute it        */
/*  and/or modify it under the terms of the GNU General Public    */
/*  License as published by the Free Software Foundation; either  */
/*  version 2 of the License, or (at your option) any later       */
/*  version.                                                      */
/*                                                                */
/*  This program is distributed in the hope that it will be       */
/*  useful, but WITHOUT ANY WARRANTY; without even the implied    */
/*  warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR       */
/*  PURPOSE.  See the GNU General Public License for more         */
/*  details.                                                      */
/*                                                                */
/*  You should have received a copy of the GNU General Public     */
/*  License along with this program; if not, write to the Free    */
/*  Software Foundation, Inc.,                                    */
/*  675 Mass Ave, Cambridge, MA 02139, USA.                       */
/*                                                                */
/******************************************************************/


/******************************************************************/
/* Includes                                                       */
/******************************************************************/

// module's interface
#include "DirWatcher.h"

// standard headers
#include <shlobj.h>
#include <tchar.h>

// project's headers
#include "Assert.h"
#include "Language.h"
#include "MainWindow.h"


/******************************************************************/
/* Internal constants                                             */
/******************************************************************/

// missing defines in shlobj.h
#define SHGFP_TYPE_CURRENT 0
#define SHGFP_TYPE_DEFAULT 1

#define FORGET_TIME 10000

#define MAX_MESSAGE_LEN		256
#define BUFFER_SIZE		1024


/******************************************************************/
/* Internal data types                                            */
/******************************************************************/

// real structure behind HDIRWATCHER
typedef struct {
  HANDLE	hThread ;
  TCHAR		szDirectory[MAX_PATH] ;
  TCHAR		szFile[MAX_PATH] ;
  BOOL		bSubDir ; 
  ADVICEID	advice ;  
} DIRWATCHER ;


/******************************************************************/
/* Internal data                                                  */
/******************************************************************/

// filter used in ReadDirectoryChangesW
static DWORD dwFilter = FILE_NOTIFY_CHANGE_FILE_NAME | /*FILE_NOTIFY_CHANGE_DIR_NAME |*/
  FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE |
  /*FILE_NOTIFY_CHANGE_LAST_ACCESS | */ FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_SECURITY ;

static ALERTID aActionToAlertId[] =
  {
    0,				// 0 = no action
    ALERT_DIR_FILEADDED,	// 1 = FILE_ACTION_ADDED
    ALERT_DIR_FILEREMOVED,	// 2 = FILE_ACTION_REMOVED
    ALERT_DIR_FILEMODIFIED,	// 3 = FILE_ACTION_MODIFIED
    ALERT_DIR_FILERENAMED,	// 4 = FILE_ACTION_RENAMED_OLD_NAME
    0,				// 5 = FILE_ACTION_RENAMED_NEW_NAME 
  } ;


/******************************************************************/
/* Internal function : Thread                                     */
/******************************************************************/

// internal functions
DWORD WINAPI _DirWatcher_Thread (LPVOID pContext)
{
  DIRWATCHER   *p = (DIRWATCHER*) pContext ;
  TCHAR	        szFile[MAX_PATH] ;
  TCHAR		szPendingOldName[MAX_PATH] = {0} ;
  BYTE		aBuffer[BUFFER_SIZE] ;
  ALERTID	iAlertId ;
  DWORD		dwBytesReturned ;
  HANDLE	hDir ;
  FILE_NOTIFY_INFORMATION * pInfo ; 

  ASSERT (p!=NULL) ;
  
  hDir = CreateFile (p->szDirectory,		// directory name
		     FILE_LIST_DIRECTORY,	// access mode
		     FILE_SHARE_READ|
		     FILE_SHARE_DELETE|
		     FILE_SHARE_WRITE,		// share mode
		     NULL,			// security descriptor
		     OPEN_EXISTING,		// how to create
		     FILE_FLAG_BACKUP_SEMANTICS,	// file attributes
		     NULL			// file with attributes to copy			    
		     ) ;

  if( hDir==INVALID_HANDLE_VALUE )
    {
      Alert_SendFormated (ALERT_ERROR, ADVICE_SEND_BUG_REPORT,
			  STR(_FAILED_TO_WATCH_DIRECTORY_S),
			  p->szDirectory) ;
      return 1 ;
    }  
  
  while(1) {

    ReadDirectoryChangesW (hDir,	// directory handle
			   aBuffer,	// buffer address
			   BUFFER_SIZE,	// buffer size
			   p->bSubDir,	// watch subtree
			   dwFilter,	// notify filter
			   &dwBytesReturned,	// bytes returned
			   NULL,	// overlapped 
			   NULL		// completion routine
			   ) ;	

    // TODO : handle the case where the buffer is too small
        
    pInfo = (FILE_NOTIFY_INFORMATION*)aBuffer ;
    
    while(1) {

      // TODO : handle the case where pInfo->FileNameLength > buffer size
           
      // copy file name
      ASSERT (pInfo->FileNameLength/sizeof(TCHAR)<MAX_PATH) ;
      memcpy (szFile, pInfo->FileName, pInfo->FileNameLength) ;
      szFile[pInfo->FileNameLength/sizeof(TCHAR)] = 0 ;

      // filter if it's not the good file
      if( p->szFile && p->szFile[0] )
	{
	  // Log_Printf (TEXT("%s ?= %s"), p->szFile,szFile) ;

	  if( _tcsicmp(p->szFile,szFile) &&
	      _tcsicmp(p->szFile,szPendingOldName) ) goto dont_notify;
	}
      
      // get the string that correspond to action
      switch( pInfo->Action )
	{
	case FILE_ACTION_RENAMED_OLD_NAME:
	  _tcscpy (szPendingOldName, szFile) ;
	  break ;
	  
	case FILE_ACTION_RENAMED_NEW_NAME:
	  Alert_Send (ALERT_DIR_FILERENAMED, p->advice,  p->szDirectory, szPendingOldName, szFile) ;
	  szPendingOldName[0] = 0 ;
	  break ;

	default:
	  if( pInfo->Action>0 && pInfo->Action<5 )
	    iAlertId = aActionToAlertId[pInfo->Action] ;
	  else
	    iAlertId = ALERT_DIR_FILEMODIFIED ;

	  // TODO : the advice should depend on the alert.
	  Alert_Send (iAlertId, p->advice,  p->szDirectory, szFile, NULL) ;
	}
            
    dont_notify:
      
      if( ! pInfo->NextEntryOffset ) break ;

      pInfo = (FILE_NOTIFY_INFORMATION*)( (BYTE*)pInfo + pInfo->NextEntryOffset ) ;      
    } 
  }
  
  return 0 ; // this will never happen
}



/******************************************************************/
/* Exported function : NewByPath                                  */
/******************************************************************/

HDIRWATCHER DirWatcher_NewByPath (LPCTSTR szDirectory, LPCTSTR szFile, BOOL bSubDir, ADVICEID advice)
{
  DIRWATCHER * p ;
  DWORD	dwThreadId ;
  
  p = malloc (sizeof(DIRWATCHER)) ;

  p->bSubDir = bSubDir ;
  lstrcpy (p->szDirectory, szDirectory) ;  
  p->advice = advice ;
  if( szFile ) lstrcpy (p->szFile, szFile) ;
  else p->szFile[0] = 0 ;

  p->hThread = CreateThread (NULL, 0, _DirWatcher_Thread, p, 0, &dwThreadId) ;
  
  return p ;
}


/******************************************************************/
/* Exported function : NewByCSIDL                                 */
/******************************************************************/

HDIRWATCHER DirWatcher_NewByCSIDL (int nFolder, LPCTSTR szFile, BOOL bSubDir, ADVICEID advice)
{
  TCHAR szPath[MAX_PATH] ;
  
  if( FAILED(SHGetFolderPath (NULL, nFolder, NULL, SHGFP_TYPE_CURRENT, szPath) ) )
    return NULL ;

  return DirWatcher_NewByPath (szPath, szFile, bSubDir, advice) ;
}

/******************************************************************/
/* Exported function : NewByCSIDLAndSubDir                        */
/******************************************************************/

HDIRWATCHER DirWatcher_NewByCSIDLAndSubDir (int nFolder, LPCTSTR szSubDir, LPCTSTR szFile, BOOL bSubDir, ADVICEID advice)
{
  TCHAR szPath[MAX_PATH] ;
  
  if( FAILED(SHGetFolderPath (NULL, nFolder, NULL, SHGFP_TYPE_CURRENT, szPath) ) )
    return NULL ;

  _tcscat (szPath, TEXT("\\")) ;  
  _tcscat (szPath, szSubDir) ;

  return DirWatcher_NewByPath (szPath, szFile, bSubDir, advice) ;
}


/******************************************************************/
/* Exported function : Delete                                     */
/******************************************************************/

VOID DirWatcher_Delete (HDIRWATCHER h)
{
  DIRWATCHER * p = (DIRWATCHER*)h ;

  if( p ) 
    {     
      // kill the thread
      // i didn't find a better solution
      TerminateThread (p->hThread, 2) ;
      CloseHandle (p->hThread) ;

      free (p) ;
    }
}
